﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;
using QTPlugin;
using QTPlugin.Interop;

namespace Kawanon.QtHoney
{
	public static class Utility
	{
		const uint GW_HWNDFIRST = 0;
		const uint GW_HWNDLAST = 1;
		const uint GW_HWNDNEXT = 2;
		const uint GW_HWNDPREV = 3;
		const uint GW_OWNER = 4;
		const uint GW_CHILD = 5;
		const uint GW_ENABLEDPOPUP = 6;

		private const uint S_OK = 0;

		const string CFSTR_SHELLIDLIST = "Shell IDList Array";

		const uint CF_HDROP = 15;

		[DllImport("user32.dll", SetLastError = true)]
		static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);

		[DllImport("user32.dll", CharSet = CharSet.Auto)]
		static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		static extern uint RegisterClipboardFormat(string lpszFormat);

		[DllImport("kernel32.dll")]
		static extern IntPtr GlobalLock(IntPtr hMem);

		[DllImport("kernel32.dll")]
		static extern bool GlobalUnlock(IntPtr hMem);

		[DllImport("ole32.dll")]
		static extern void ReleaseStgMedium(ref STGMEDIUM pmedium);

		[DllImport("shell32.dll")]
		static extern IntPtr ILClone(IntPtr pidl);

		[DllImport("shell32.dll")]
		static extern IntPtr ILCombine(IntPtr pidl1_ABSOLUTE, IntPtr pidl2_RELATIVE);

		[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
		static extern IntPtr ILCreateFromPath(string pszPath);

		[DllImport("shell32.dll")]
		static extern IntPtr ILFindLastID(IntPtr pidl);

		[DllImport("shell32.dll")]
		static extern uint ILGetSize(IntPtr pidl);

		[DllImport("shell32.dll")]
		static extern void ILFree(IntPtr pidl_RELATIVE);

		[DllImport("shell32.dll")]
		static extern uint DragQueryFile(IntPtr hDrop, uint iFile, [Out] StringBuilder lpszFile, uint cch);

		[DllImport("shell32.dll")]
		static extern uint DragQueryFile(IntPtr hDrop, uint iFile, IntPtr zero, uint cch);

		[DllImport("ole32.dll")]
		static extern IntPtr CoTaskMemAlloc(uint cb);

		[DllImport("ole32.dll")]
		static extern void CoTaskMemFree(IntPtr pv);

		[DllImport("shell32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool SHGetPathFromIDListW(IntPtr pidl, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);

		private struct SHFILEINFO
		{
			public IntPtr hIcon;
			public IntPtr iIcon;
			public uint dwAttributes;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
			public string szDisplayName;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
			public string szTypeName;
		};

		private const uint SHGFI_ICON = 0x100; // アイコン・リソースの取得
		private const uint SHGFI_LARGEICON = 0x0; // 大きいアイコン
		private const uint SHGFI_SMALLICON = 0x1; // 小さいアイコン
		private const uint SHGFI_SYSICONINDEX = 0x000004000;
		private const uint SHGFI_DISPLAYNAME = 0x000000200;

		[DllImport("shell32.dll")]
		private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);

		[DllImport("user32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool DestroyIcon(IntPtr hIcon);

		//[StructLayout(LayoutKind.Sequential)]
		//struct CIDA
		//{
		//    /// <summary>
		//    ///  Number of PIDLs that are being transferred, not counting the parent folder.
		//    /// </summary>
		//    public uint cidl;

		//    /// <summary>
		//    /// An array of offsets, relative to the beginning of this structure. The array contains
		//    /// cidl+1 elements. The first element of aoffset contains an offset to the fully-qualified
		//    /// PIDL of a parent foloder. If this PIDL is empty, the parent folder is the desktop.
		//    /// Each of the remaining elements of the array contains an offset to one of the PIDLs to be
		//    /// transferred. ALL of these PIDLs are relative to the PIDL of the parent folder.
		//    /// </summary>
		//    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
		//    public uint[] aoffset;
		//}

		public static void SetFieldValue(object obj, string name, object val)
		{
			FieldInfo field = obj.GetType().GetField(name,
				BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
			field.SetValue(obj, val);
		}

		public static object GetFieldValue(object obj, string name)
		{
			FieldInfo field = obj.GetType().GetField(name,
				BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
			return field.GetValue(obj);
		}

		//public static Stream GetResourceStream(string name)
		//{
		//    Assembly asm = Assembly.GetExecutingAssembly();
		//    string str = String.Format("{0}.res.{1}", typeof(Utility).Namespace, name);
		//    return asm.GetManifestResourceStream(str);
		//}

		public static void ShowForm(IPluginServer server, Form form)	// from GroupEditor
		{
			RECT rc = new RECT();
			if (GetWindowRect(server.ExplorerHandle, out rc))
			{
				form.StartPosition = FormStartPosition.Manual;
				form.Left = rc.left + (rc.Width - form.Width) / 2;
				form.Top = rc.top + (rc.Height - form.Height) / 2;
			}
			else
			{
				form.StartPosition = FormStartPosition.CenterScreen;
			}

			// ツールバーウィンドウを親に（ExplorerHandle は null が返るので）
			Control parent = Control.FromHandle(server.Handle);
			form.Show(parent);

			//MessageBox.Show(string.Format("{0} {1} {2} {3}", rc.right, rc.top, parent.Width, parent.Height ));
		}

		public static Control GetBandObject(IPluginServer server)
		{
			// QTTabBarClass を取得
			return Control.FromHandle(server.Handle);
		}

		public static IShellBrowser GetShellBrowser(IPluginServer server)
		{
			return (IShellBrowser)GetFieldValue(server, "shellBrowser");
		}

		public static bool IsDebugMode()
		{
			RegistryKey key = Plugins.OpenRegistryKey(false);
			if (key == null)
			{
				return false;
			}
			int val = (int)key.GetValue("Debug", 0);
			return val != 0;
		}

		public delegate T ModalInvoker<T>();

		public static T DoModal<T>(IPluginServer server, ModalInvoker<T> func)
		{
			if (server != null)
			{
				server.ExecuteCommand(Commands.SetModalState, true);
			}
			try
			{
				return func();
			}
			finally
			{
				if (server != null)
				{
					server.ExecuteCommand(Commands.SetModalState, false);
				}
			}
		}

		public static DialogResult DoModal(IPluginServer server, Form form)
		{
			if (server != null)
			{
				server.ExecuteCommand(Commands.SetModalState, true);
			}
			try
			{
				return form.ShowDialog();
			}
			finally
			{
				if (server != null)
				{
					server.ExecuteCommand(Commands.SetModalState, false);
				}
			}
		}

		public static Predicate<IntPtr> EqualsClassName(string name)
		{
			return delegate(IntPtr hwnd)
			{
				StringBuilder buf = new StringBuilder(32);
				if (GetClassName(hwnd, buf, 32) == 0)
				{
					return false;
				}
				return buf.ToString() == name;
			};
		}

		public static IntPtr FindChildWindow(IntPtr hwnd, Predicate<IntPtr> pred)
		{
			IntPtr child = GetWindow(hwnd, GW_CHILD);
			while (child != IntPtr.Zero)
			{
				if (pred(child))
				{
					return child;
				}
				// 孫以下を検索 (フォルダに共通の作業を表示する の場合など)
				IntPtr ret = FindChildWindow(child, pred);
				if (ret != IntPtr.Zero)
				{
					return ret;
				}

				child = GetWindow(child, GW_HWNDNEXT);
			}
			return IntPtr.Zero;
		}

		private delegate T ExtractHGlobalHandler<T>(IntPtr hGlobal);

		private static T ExtractHGlobal<T>(System.Runtime.InteropServices.ComTypes.IDataObject data,
			uint cfFormat, ExtractHGlobalHandler<T> handler)
			where T : class
		{
			FORMATETC format = new FORMATETC();
			format.cfFormat = (short)cfFormat;
			format.dwAspect = DVASPECT.DVASPECT_CONTENT;
			format.lindex = -1;
			format.tymed = TYMED.TYMED_HGLOBAL;

			if (data.QueryGetData(ref format) != S_OK)
			{
				return null;
			}
			STGMEDIUM medium = new STGMEDIUM();
			data.GetData(ref format, out medium);

			IntPtr hGlobal = GlobalLock(medium.unionmember);
			if (hGlobal == IntPtr.Zero)
			{
				return null;
			}

			T ret;
			try
			{
				ret = handler.Invoke(hGlobal);
			}
			finally
			{
				GlobalUnlock(hGlobal);
				ReleaseStgMedium(ref medium);
			}
			return ret;
		}

		public static string[] GetFileNames(System.Runtime.InteropServices.ComTypes.IDataObject data)
		{
			return ExtractHGlobal(data, CF_HDROP, delegate(IntPtr hDrop)
			{
				List<string> list = new List<string>();
				StringBuilder buf = new StringBuilder(260);
				uint count = DragQueryFile(hDrop, UInt32.MaxValue, IntPtr.Zero, 0);
				for (uint i = 0; i < count; i++)
				{
					uint len = DragQueryFile(hDrop, i, IntPtr.Zero, 0);
					buf.Capacity = Math.Max(buf.Capacity, (int)len);

					if (DragQueryFile(hDrop, i, buf, (uint)buf.Capacity) != len)
					{
						Dbg.Abort("DragQueryFile length error");
					}
					list.Add(buf.ToString());
				}
				return list.ToArray();
			});
		}

		public static Address[] GetAddresses(System.Runtime.InteropServices.ComTypes.IDataObject data)
		{
			return ExtractHGlobal(data, RegisterClipboardFormat(CFSTR_SHELLIDLIST), delegate(IntPtr hGlobal)
			{
				int intSize = Marshal.SizeOf(typeof(int));
				int cidl = Marshal.ReadInt32(hGlobal, 0);

				List<IntPtr> pointers = new List<IntPtr>();
				for (int i = 0; i < cidl + 1; i++)
				{	// 親パスも含める
					int offset = Marshal.ReadInt32(hGlobal, (i + 1) * intSize);	// cidl をスキップ
					//byte[] bytes = new byte[offset - prev];
					//Marshal.Copy(new IntPtr( (int)hGlobal + offset ), bytes, 0, bytes.Length);
					//list.Add(bytes);
					//prev = offset;

					//Address addr = new Address(new IntPtr((int)hGlobal + offset));
					//addr.ToString();
					pointers.Add(new IntPtr((int)hGlobal + offset));
				}
				IntPtr parent = pointers[0];
				pointers.RemoveAt(0);
				pointers = pointers.ConvertAll(ptr => ILCombine(parent, ptr));

				List<Address> addrs = pointers.ConvertAll(ptr => new Address(ptr));
				pointers.ForEach(ptr => ILFree(ptr));	// 解放

				return addrs.ToArray();
			});
		}

		public static string AddressToPath(Address addr)
		{
			if (addr.Path != null)
			{
				//Dbg.Echo(addr.Path);
				return addr.Path;
			}
			if (addr.ITEMIDLIST == null)
			{
				//Dbg.Abort("ITEMIDLIST is null");
				return null;
			}

			return PidlToPath(addr.ITEMIDLIST);
		}

		public static string PidlToPath(byte[] bytes)
		{
			int len = bytes.Length;
			IntPtr pidl = CoTaskMemAlloc((uint)len);
			Marshal.Copy(bytes, 0, pidl, len);

			string path = PidlToPath(pidl);
			CoTaskMemFree(pidl);
			return path;
		}

		public static string PidlToPath(IntPtr pidl)
		{
			StringBuilder buf = new StringBuilder(260);
			if (!SHGetPathFromIDListW(pidl, buf))	//NOTE: 仮想フォルダはたぶん無理
			{
				return null;
			}
			return buf.ToString();
		}

		public static string Quote(string str)
		{
			return '"' + str + '"';
		}

		public static uint MakeLong(uint a, uint b)
		{
			return ((uint)(((ushort)(a)) | ((uint)((ushort)(b))) << 16));
		}

		public static bool IsSpecialFolder(string path)
		{
			return path.StartsWith("::{");
		}

		public static string GetDisplayName(string path)
		{
			SHFILEINFO sfi = new SHFILEINFO();
			if (SHGetFileInfo(path, 0, ref sfi, (uint)Marshal.SizeOf(sfi), SHGFI_DISPLAYNAME) != IntPtr.Zero)
			{
				return sfi.szDisplayName;
			}
			return path;
		}

		public static Bitmap GetIconBitmap(string path, bool large)
		{
			SHFILEINFO sfi = new SHFILEINFO();
			if (SHGetFileInfo(path, 0, ref sfi, (uint)Marshal.SizeOf(sfi),
				SHGFI_ICON | (large ? SHGFI_LARGEICON : SHGFI_SMALLICON)) != IntPtr.Zero)
			{
				Icon icon = Icon.FromHandle(sfi.hIcon);
				Bitmap image = icon.ToBitmap();
				// 元のアイコンは破棄
				if (!DestroyIcon(sfi.hIcon))
				{
					Dbg.Abort("DestroyIcon Failed");
				}
				return image;
			}
			return null;
		}

		public static void Raise(EventHandler handler)
		{
			if (handler != null)
			{
				handler.Invoke(null, new EventArgs());
			}
		}

		public static T ChooseFromArray<T>(IWin32Window owner, T[] array)
			where T : class
		{
			switch (array.Length)
			{
				case 0:
					return null;
				case 1:
					return array[0];
			}

			ChooseFromArrayForm form = new ChooseFromArrayForm();
			form.Source = array;
			DialogResult res = form.ShowDialog(owner);
			if (res == DialogResult.Cancel)
			{
				return null;
			}
			return (T)form.Selected;
		}

		public static EventHandler ToEventHandler(MethodInvoker func)
		{
			return delegate(object sender, EventArgs e)
			{
				func();
			};
		}

		public static byte[] PidlFromAddress(Address addr)
		{
			if (addr.ITEMIDLIST != null)
			{
				return addr.ITEMIDLIST;
			}

			if (addr.Path == null)
			{
				return null;
			}

			IntPtr pidl = ILCreateFromPath(addr.Path);
			if (pidl == IntPtr.Zero)
			{
				return null;
			}

			int size = (int)ILGetSize(pidl);
			byte[] bytes = new byte[size];
			Marshal.Copy(pidl, bytes, 0, size);

			ILFree(pidl);
			return bytes;
		}

		public static int CountOf<T>(IEnumerable<T> e)
		{
			int i = 0;
			foreach (object o in e)
			{
				i++;
			}
			return i;
		}

		public static object GetRegistryValue(string name)
		{
			RegistryKey reg = Plugins.OpenRegistryKey(false);
			if (reg == null)
			{
				return null;
			}
			object obj = reg.GetValue(name);
			reg.Close();

			return obj;
		}

		public static void SetRegistryValue(string name, object val)
		{
			RegistryKey key = Plugins.OpenRegistryKey(true);
			key.SetValue(name, val);
			key.Flush();
			key.Close();
		}

		public static void UseWaitCursor(MethodInvoker func)
		{
			Cursor cur = Cursor.Current;
			Cursor.Current = Cursors.WaitCursor;
			try
			{
				func();
			}
			finally
			{
				Cursor.Current = cur;
			}
		}
	}
}
